<?php
namespace App\Baqi\Sdpay;

class Sdpay 
{
    // 基础属性
    public $version      = '1.0';            // 版本号
    public $method       = "";              // 接口名称
    public $productId    = "";              // 产品编码
    public $accessType   = '1';             // 接入类型
    public $mid          = 16797484;      // 商户ID
    public $subMid       = '';              // 二级商户ID
    public $subMidName   = '';              // 二级商户名称
    public $subMidAddr   = '';              // 二级商户简称
    public $channelType  = '';              // 渠道类型
    public $reqTime      = '';               // 请求时间

   public $url = 'https://cashier.sandpay.com.cn/gateway/api';

    //public $url = 'http://61.129.71.103:8003/gateway/api';

    public $pub_key; //公钥
    public $pri_key; //私钥
    public $pwd          = '123456';//私钥密码

    public $type; // 支付类型


    // 业务属性
    public $orderCode;//商户订单号
    public $totalAmount;//订单金额
    public $subject;//订单标题
    public $body;//订单描述
    public $txnTimeOut;//订单超时时间
    public $payMode;//支付模式
    public $payExtra;//支付扩展域
    public $clientIp;//客户端IP
    public $notifyUrl;//异步通知地址
    public $frontUrl;//前台通知地址
    public $storeId;//商户门店编号
    public $terminalId;//商户终端编号
    public $operatorId;//操作员编号
    public $bizExtendParams;//业务扩展参数
    public $merchExtendParams;//商户扩展参数
    public $extend;//扩展域


    public $config = [];//需要签名的 所有参数
    /**
     * 初始化配置
     * @param [type] $data['method'接口名称   'productId' 产品编码  'mid' 商户id] 
     */
    public function __construct($type = 'H5') 
    {
        $this->type    =  $type;

        $this->pub_key =  base_path('app/Baqi/Sdpay/sand.cer');
        $this->pri_key =  base_path('app/Baqi/Sdpay/mid.pfx');
    }

    public function getData($url = '/order/pay') 
    {

        $request = json_encode($this->config);

        $sign = $this->sign($request);

        $params = array(
            'charset'  => 'utf-8',
            'signType' => '01',
            'data'     => $request,
            'sign'     => $sign
        );

        $query = http_build_query($params);

        $response = $this->request_post($this->url.$url, $query);
        parse_str(urldecode($response), $arr);
        //errorHandle::log(json_encode($arr));

        //base64编码中的+经过urldecode会转成空格 需要替换成+
        $arr['sign'] = str_replace(' ', '+', $arr['sign']);

        //orderPay接口返回的credential字段里面的sign也是base64编码 也需将空格替换成+
        $data = json_decode($arr['data'], true);
        if ($data && isset($data['body']['credential'])) {
            $credential = json_decode($data['body']['credential'], true);
            if ($credential && is_array($credential['params']) && isset($credential['params']['sign'])) {
                //直接将整体中的空格替换成+ 会造成误替换
                //定位到sign字段后 空格替换成自定义字符串*&^ 最后整体将*&^替换成+
                $credential['params']['sign'] = str_replace(' ', '*&^', $credential['params']['sign']);
                $credential['params']['name_goods'] = urlencode($credential['params']['name_goods']);
                $credential['params']['info_order'] = urlencode($credential['params']['info_order']);
                $data['body']['credential'] = str_replace("\\/", "/", json_encode($credential));
                $data['head']['respMsg'] = urlencode($data['head']['respMsg']);
                $arr['data'] = str_replace(array("\\/", '*&^'), array("/", '+'), urldecode(json_encode($data)));
            }
        }
        //验签
        $this->verify($arr['data'], $arr['sign']);

        $data = json_decode($arr['data'], true);

        $credential = '';
        if ($data['head']['respCode'] == "000000" ) {
            $credential = $data['body']['credential'];
        } else {
            errorHandle::throwException(new \Exception('opps!'));
        }

        $html = $this->getHtml($credential);

        return $html;
    }

    public function getHtml($credential) 
    {
                return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
                <html>
                <head>
                    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
                    <title>Insert title here</title>
                    <script type="text/javascript" src="/paymentjs.js"></script>
                    <script type="text/javascript" src="/jquery-1.7.2.min.js"></script>
                </head>
                <body>
                <script>
                    function wap_pay() {
                        var responseText = $("#credential").text();
                        console.log(responseText);
                        paymentjs.createPayment(responseText, function(result, err) {
                            console.log(result);
                            console.log(err.msg);
                            console.log(err.extra);
                        });
                    }
                </script>
                <div style="display: none" >
                    <p id="credential">'.$credential.'</p>
                </div>
                </body>

                <script>
                    window.onload=function(){
                        wap_pay();
                    };
                </script>
                </html>';
    }


    /**
     * 初始化参数
     */
    public function setConfig($data) 
    {

        $this->config['head'] = $this->getConfig();

        $this->config['body'] = $this->getBusiness($data);
    }

    /** 
     * [获得配置参数]
     */
    public function getConfig() 
    {
        switch ($this->type) {
            case 'H5':
                $config = $this->h5Config();
                break;
            
            default:
                # code...
                break;
        }

        return $config;
    }


    //设置配置参数
    public function h5Config() 
    {
        return array (
            'version'      => $this->version,
            'method'       => 'sandpay.trade.pay',
            'productId'    => '00000008',
            'accessType'   => $this->accessType,
            'mid'          => $this->mid,
            'subMid'       => '',
            'subMidName'   => '',
            'subMidAddr'   => '',
            'channelType'  => '07',
            'reqTime'      => date('YmdHis', time()),
            //'reqTime'      => '20170705120056'
        );
        
    }
    //设置业务参数
    public function getBusiness($data) 
    {
        return array (
            'orderCode'         => $data['order_sn'],
            'totalAmount'       => $this->convertModeny($data['money']),
            'subject'           => $data['order_sn'],
            'body'              => '商品购买',
            'txnTimeOut'        => date('YmdHis',time()+60*60),
            'payMode'           => $this->getPayModel(),
            'payExtra'          => '',
            'clientIp'          => $this->getip(),
            'notifyUrl'         => $data['notifyUrl'],
            'frontUrl'          => $data['frontUrl'],
            'storeId'           => '',
            'terminalId'        => '',
            'operatorId'        => '',
            'bizExtendParams'   => '',
            'merchExtendParams' => '',
            'extend'            => '',
        );
    }

    public function convertModeny($money)
    {
        $money_int = intval($money * 100);
        $leng      = 12 - strlen($money_int);

        $zro = '';
        for ($i=1; $i<= $leng; $i++) {

            $zro .= '0';
        }
        return $zro.$money_int;
    }

    //获得支付模型
    public function getPayModel() 
    {
        switch ($this->type) {
            case 'H5':
                $model = 'sand_h5';
                break;
            
            default:
                # code...
                break;
        }
        return $model;
    }
    /**
     * 获取IP地址
     */
    private function getip() {
        if (isset($_SERVER)) {
            if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
               $realip = $_SERVER['HTTP_X_FORWARDED_FOR'];
            } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
               $realip = $_SERVER['HTTP_CLIENT_IP'];
            } else {
               $realip = $_SERVER['REMOTE_ADDR'];
            }
        } else {
            if (getenv("HTTP_X_FORWARDED_FOR")) {
               $realip = getenv( "HTTP_X_FORWARDED_FOR");
            } elseif (getenv("HTTP_CLIENT_IP")) {
               $realip = getenv("HTTP_CLIENT_IP");
            } else {
               $realip = getenv("REMOTE_ADDR");
            }
        }
        return $realip;
    }



    /**
     * 公钥验签
     * @param $plainText
     * @param $sign
     * @return int
     * @throws \Exception
     */
    public function verify($plainText, $sign) {

        $pub_key = $this->getPubKey();

        $resource = openssl_pkey_get_public($pub_key);
        $result  = openssl_verify($plainText, base64_decode($sign), $resource);
        openssl_free_key($resource);

        if (!$result) {//todo cancel annotation
            //echo '签名验证未通过';die;
            errorHandle::throwException(new \Exception('签名验证未通过,plainText:' . $plainText . '。sign:' . $sign, '020002'));
        }

        return $result;
    }

    public  function request_post($url = '', $param = '') {
        if (empty($url) || empty($param)) {
            return false;
        }
        try {
            $ch = curl_init();//初始化curl
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
            curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);  
            $data = curl_exec($ch);//运行curl

            curl_close($ch);
            if (!$data) {
                throw new \Exception('请求出错');
            }

            return $data;
        } catch (\Exception $e) {
            errorHandle::log($e);
            throw $e;
        }
    }

    /**
     * 私钥签名
     * @param $plainText
     * @return string
     * @throws \Exception
     */
    public function sign($plainText) {

        $pri_key = $this->getPriKey();
        try {
            $resource = openssl_pkey_get_private($pri_key);
            $result = openssl_sign($plainText, $sign, $resource);
            openssl_free_key($resource);

            if (!$result) {
                throw new \Exception('签名出错' . $plainText);
            }

            return base64_encode($sign);
        } catch (\Exception $e) {
            errorHandle::log($e);
            throw $e;
        }
    }

    /**
     * 获取公钥
     * @param $path
     * @return mixed
     * @throws \Exception
     */
    private function getPubKey() {


        try {
            $file = file_get_contents($this->pub_key);
            if (!$file) {
                throw new \Exception('loadX509Cert::file_get_contents ERROR');
            }

            $cert = chunk_split(base64_encode($file), 64, "\n");
            $cert = "-----BEGIN CERTIFICATE-----\n" . $cert . "-----END CERTIFICATE-----\n";

            $res = openssl_pkey_get_public($cert);
            $detail = openssl_pkey_get_details($res);
            openssl_free_key($res);

            if (!$detail) {
                throw new \Exception('loadX509Cert::openssl_pkey_get_details ERROR');
            }

            return $detail['key'];
        } catch (\Exception $e) {
            errorHandle::log($e);
            throw $e;
        }
    }

    /**
     * 获取私钥
     * @param $path
     * @param $pwd
     * @return mixed
     * @throws \Exception
     */
    private function getPriKey() {

        
        try {
            $file = file_get_contents($this->pri_key);
            if (!$file) {
                throw new \Exception('loadPk12Cert::file_get_contents ERROR');
            }

            if (!openssl_pkcs12_read($file, $cert, $this->pwd)) {
                throw new \Exception('loadPk12Cert::openssl_pkcs12_read ERROR');
            }

            return $cert['pkey'];
        } catch (\Exception $e) {
            errorHandle::log($e);
            throw $e;
        }
    }
}

class errorHandle {
    public static function log($msg) {
        echo $msg;
        //error_log(date("[Y-m-d H:i:s]") . " -[ error : " . $msg . " \n", 3, base_path("app/Baqi/sdpay/sd_plug_err.log"));
    }

    public static function throwException(\Exception $e) {
        echo json_encode(
            array(
                'respCode' => $e->getCode(),
                'respDesc' => $e->getMessage()
            )
        );
        exit;
    }
}
?>